/* ****************************************************************************
 *
 * Copyright (c) Microsoft Corporation. 
 *
 * This source code is subject to terms and conditions of the Microsoft Permissive License. A 
 * copy of the license can be found in the License.html file at the root of this distribution. If 
 * you cannot locate the  Microsoft Permissive License, please send an email to 
 * ironpy@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 * by the terms of the Microsoft Permissive License.
 *
 * You must not remove this notice, or any other, from this software.
 *
 *
 * ***************************************************************************/


using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Diagnostics;

using Microsoft.Scripting.Internal.Generation;
using Microsoft.Scripting.Internal.Ast;

using Microsoft.Scripting;
using Microsoft.Scripting.Internal;
using Microsoft.Scripting.Actions;
using System.Threading;

// for the method binder
using IronPython.Compiler;

namespace Nua.Runtime
{
    public class DoOperationBinderHelper<T> : BinderHelper<T> 
    {
        private ActionBinder _binder;
        protected DoOperationAction _action;
        private CodeContext _context;

        public DoOperationBinderHelper(ActionBinder binder, CodeContext context, DoOperationAction action) {
            this._binder = binder;
            this._action = action;
            this._context = context;
        }

        public StandardRule<T> MakeRule(object[] args) {
            return MakeNewRule(CompilerHelpers.ObjectTypes(args));
        }

       protected StandardRule<T> MakeNewRule(DynamicType[] types) {
            for (int i = 0; i < types.Length; i++) {
                if (types[i].Version == DynamicType.DynamicVersion) {
                    //return MakeDynamicMatchRule(types);
                }
            }

            Operators op = _action.Operation;
            if (_action.IsInPlace) {
                DynamicType xType = types[0];
                DynamicTypeSlot xSlot;
                if (xType.TryLookupSlot(_context, Symbols.OperatorToSymbol(op), out xSlot)) {
                    // TODO optimize calls to explicit inplace methods
                    //return MakeDynamicMatchRule(types);
                }
                op = _action.DirectOperation;
            }

            if (_action.IsUnary) {
                Debug.Assert(types.Length == 1);
                //return MakeUnaryRule(types, op);
            }

            //if (types[0] == TypeCache.Object && types[1] == TypeCache.Object) {
                //return MakeDynamicMatchRule(types);
            //}

            if (IsComparision) {
                //return MakeComparisonRule(types, op);
            }
            return MakeSimpleRule(types, op);
        }

        private StandardRule<T> MakeSimpleRule(DynamicType[] types, Operators oper) {
            SymbolId op = Symbols.OperatorToSymbol(oper);
            SymbolId rop = Symbols.OperatorToReversedSymbol(oper);

            MethodBinder fbinder, rbinder;
            if (!TryGetBinder(types, op, SymbolId.Empty, out fbinder)) {
                return MakeDynamicMatchRule(types);
            }

            if (!TryGetBinder(types, SymbolId.Empty, rop, out rbinder)) {
                return MakeDynamicMatchRule(types);
            }

            MethodCandidate fCand = ComparisonTargetFromBinder(fbinder, types);
            MethodCandidate rCand = ComparisonTargetFromBinder(rbinder, types);

            return MakeRuleForBinaryOperator(types, oper, fCand, rCand);
        }

        private StandardRule<T> MakeDynamicMatchRule(DynamicType[] types) {
            //TODO figure out caching strategy for these
            StandardRule<T> ret = new StandardRule<T>();
            ret.MakeTest(types);
            if (_action.IsUnary) {
                //MakeDynamicTarget(DynamicInvokeUnaryOperation, ret);
            } else if (_action.IsInPlace) {
                //MakeDynamicTarget(DynamicInvokeInplaceOperation, ret);
            } else if (IsComparision) {
                //MakeDynamicTarget(DynamicInvokeCompareOperation, ret);
            } else {
                //MakeDynamicTarget(DynamicInvokeBinaryOperation, ret);
            }

            return ret;
        }

        private delegate object DynamicOperationMethod(CodeContext context, Operators op, object x, object y);
        private delegate object DynamicUnaryOperationMethod(CodeContext context, Operators op, object x);
        private void MakeDynamicTarget(DynamicOperationMethod action, StandardRule<T> rule) {
            Expression expr = MethodCallExpression.Call(null, action.Method,
                    new CodeContextExpression(),
                    ConstantExpression.Constant(this._action.Operation),
                    rule.GetParameterExpression(0),
                    rule.GetParameterExpression(1));
            rule.SetTarget(rule.MakeReturn(_binder, expr));
        }
        private void MakeDynamicTarget(DynamicUnaryOperationMethod action, StandardRule<T> rule) {
            Expression expr = MethodCallExpression.Call(null, action.Method,
                    new CodeContextExpression(),
                    ConstantExpression.Constant(this._action.Operation),
                    rule.GetParameterExpression(0));
            rule.SetTarget(rule.MakeReturn(_binder, expr));
        }

        private MethodCandidate ComparisonTargetFromBinder(MethodBinder binder, DynamicType[] types) {
            if (binder == null) return null;
            return binder.MakeBindingTarget(CallType.None, types);
        }


        private StandardRule<T> MakeComparisonRule(DynamicType[] types, Operators op) {
            DynamicType xType = types[0];
            DynamicType yType = types[1];
            SymbolId opSym = Symbols.OperatorToSymbol(op);
            SymbolId ropSym = Symbols.OperatorToReversedSymbol(op);

            MethodBinder fbind, rbind, cbind, rcbind;
            // forward
            if (!TryGetBinder(types, opSym, SymbolId.Empty, out fbind)) {
                return MakeDynamicMatchRule(types);
            }

            // reverse
            DynamicType[] rTypes = new DynamicType[] { types[1], types[0] };
            if (!TryGetBinder(rTypes, ropSym, SymbolId.Empty, out rbind)) {
                return MakeDynamicMatchRule(types);
            }

            // __cmp__ 
            //if (!TryGetBinder(types, Symbols.Cmp, SymbolId.Empty, out cbind)) {
            //    return MakeDynamicMatchRule(types);
            //}

            // reversed __cmp__ 
            //if (!TryGetBinder(rTypes, Symbols.Cmp, SymbolId.Empty, out rcbind)) {
            //    return MakeDynamicMatchRule(types);
            //}

            // fallback binder, depending on what comparison call a helper
            // which always yields a value.
            MethodCandidate forward = ComparisonTargetFromBinder(fbind, types);
            MethodCandidate reverse = ComparisonTargetFromBinder(rbind, rTypes);
            //MethodCandidate fcmp = ComparisonTargetFromBinder(cbind, types);
            //MethodCandidate rcmp = ComparisonTargetFromBinder(rcbind, rTypes);

            MethodTarget fTarget, rTarget, fCmpTarget, rCmpTarget;
            //GetCombinedTargets(forward, reverse, out fTarget, out rTarget);
            //GetCombinedTargets(fcmp, rcmp, out fCmpTarget, out rCmpTarget);

            StandardRule<T> rule = new StandardRule<T>();
            rule.MakeTest(types);

            List<Statement> stmts = new List<Statement>();
            //if (MakeOneTarget(fTarget, rule, stmts, false)) {
            //    if (MakeOneTarget(rTarget, rule, stmts, true)) {
            //        if (MakeOneCompare(fCmpTarget, rule, stmts, false)) {
            //            if (MakeOneCompare(rCmpTarget, rule, stmts, true)) {
            //                stmts.Add(MakeFallbackCompare(rule));
            //            }
            //        }
            //    }
            // }

            rule.SetTarget(BlockStatement.Block(stmts));
            return rule;
        }

        private StandardRule<T> MakeRuleForBinaryOperator(DynamicType[] types, Operators oper, MethodCandidate fCand, MethodCandidate rCand) {
            MethodTarget fTarget, rTarget;

            if (!GetCombinedTargets(fCand, rCand, out fTarget, out rTarget)) {
                //return MakeRuleForNoMatch(types);
                throw new NotImplementedException();
            }

            StandardRule<T> rule = new StandardRule<T>();
            rule.MakeTest(types);

            List<Statement> stmts = new List<Statement>();
            if (MakeOneTarget(fTarget, rule, stmts, false)) {
                if (MakeOneTarget(rTarget, rule, stmts, false)) {
                    stmts.Add(MakeBinaryThrow(rule));
                }
            }
            rule.SetTarget(BlockStatement.Block(stmts));
            return rule;
        }

        private Expression MakeCall(MethodTarget target, StandardRule<T> block, bool reverse) {
            VariableReference[] vars = block.Parameters;
            if (reverse) {
                VariableReference[] newVars = new VariableReference[2];
                newVars[0] = vars[1];
                newVars[1] = vars[0];
                vars = newVars;
            }
            return target.MakeExpression(_binder, vars);
        }

        private bool MakeOneTarget(MethodTarget target, StandardRule<T> block, List<Statement> stmts, bool reverse) {
            if (target == null) return true;

            stmts.Add(block.MakeReturn(_binder, MakeCall(target, block, reverse)));
            return false;
   
        }

    
        private Statement MakeBinaryThrow(StandardRule<T> block) {
            return new ExpressionStatement(new ThrowExpression(
                MethodCallExpression.Call(null, typeof(NuaOps).GetMethod("NotImplementedError"),
                    ConstantExpression.Constant(SymbolTable.IdToString(Symbols.OperatorToSymbol(_action.Operation))),
                    block.GetParameterExpression(0), block.GetParameterExpression(1))));
        }

                /// <summary>
        /// Gets the logically combined targets.  If the 1st target is preferred over the 2nd one 
        /// we'll return both.
        /// </summary>
        private static bool GetCombinedTargets(MethodCandidate fCand, MethodCandidate rCand, out MethodTarget fTarget, out MethodTarget rTarget) {
            fTarget = rTarget = null;

            if (fCand != null) {
                if (rCand != null) {
                    if (fCand.NarrowingLevel <= rCand.NarrowingLevel) {
                        fTarget = fCand.Target;
                        rTarget = rCand.Target;
                    } else {
                        fTarget = null;
                        rTarget = rCand.Target;
                    }
                } else {
                    fTarget = fCand.Target;
                }
            } else if (rCand != null) {
                rTarget = rCand.Target;
            } else {
                return false;
            }
            return true;
        }


              
        private bool TryGetBinder(DynamicType[] types, SymbolId op, SymbolId rop, out MethodBinder binder) {
            DynamicType xType = types[0];
            DynamicType yType = types[1];
            binder = null;

            BuiltinFunction xBf;
            if (!TryGetStaticFunction(op, xType, out xBf)) {
                return false;
            }

            BuiltinFunction yBf = null;
            if (!xType.IsSubclassOf(yType) && !TryGetStaticFunction(rop, yType, out yBf)) {
                return false;
            }

            if (yBf == xBf) {
                yBf = null;
            } else if (yBf != null && yType.IsSubclassOf(xType)) {
                xBf = null;
            }

            if (xBf == null) {
                if (yBf == null) {
                    binder = null;
                } else {
                    binder = MethodBinder.MakeBinder(_binder, op.ToString(),
                        yBf.Targets, MethodBinderType);
                }
            } else {
                if (yBf == null) {
                    binder = MethodBinder.MakeBinder(_binder, op.ToString(),
                        xBf.Targets, MethodBinderType);
                } else {
                    List<MethodBase> targets = new List<MethodBase>();
                    targets.AddRange(xBf.Targets);
                    foreach (MethodBase mb in yBf.Targets) {
                        if (!ContainsMethodSignature(targets, mb)) targets.Add(mb);
                    }
                    binder = MethodBinder.MakeBinder(_binder, op.ToString(),
                        targets.ToArray(), MethodBinderType);
                }
            }
            return true;
        }

        private BinderType MethodBinderType {
            get {
                return IsComparision ? BinderType.ComparisonOperator : BinderType.BinaryOperator;
            }
        }

        public bool IsComparision {
            get {
                return _action.IsComparision;
            }
        }

        private static bool ContainsMethodSignature(IList<MethodBase> existing, MethodBase check) {
            ParameterInfo[] pis = check.GetParameters();
            foreach (MethodBase mb in existing) {
                if (MatchesMethodSignature(pis, mb)) return true;
            }
            return false;
        }

        private static bool MatchesMethodSignature(ParameterInfo[] pis, MethodBase mb) {
            ParameterInfo[] pis1 = mb.GetParameters();
            if (pis.Length == pis1.Length) {
                for (int i = 0; i < pis.Length; i++) {
                    if (pis[i].ParameterType != pis1[i].ParameterType) return false;
                }
                return true;
            } else {
                return false;
            }
        }

        private bool TryGetStaticFunction(SymbolId op, DynamicType type, out BuiltinFunction function) {
            function = null;
            if (op != SymbolId.Empty) {
                DynamicTypeSlot xSlot;
                object val;
                if (type.TryResolveSlot(_context, op, out xSlot) &&
                    xSlot.TryGetValue(_context, null, type, out val)) {
                    function = TryConvertToBuiltinFunction(val);
                    if (function == null) return false;
                }
            }
            return true;
        }

        private string GetName() {
            switch(_action.Operation) {
                case Operators.Add: return "Add";
                case Operators.Subtract: return "Subtract";
                case Operators.Multiply: return "Multiply";
                case Operators.Divide: return "Divide";
                default: throw new NotImplementedException();
            }
        }

        private string GetSpecialName() {
            switch (_action.Operation) {
                case Operators.Add: return "op_Addition";
                case Operators.Subtract: return "op_Subtraction";
                case Operators.Multiply: return "op_Multiplication";
                case Operators.Divide: return "op_Division";
                default: throw new NotImplementedException();
            }
        }

        public static BuiltinFunction GetBuiltinFunction(DynamicTypeSlot dts) {
            if (dts is BuiltinFunction)
                return (BuiltinFunction)dts;

            BuiltinMethodDescriptor bmd = dts as BuiltinMethodDescriptor;
            if (bmd != null) {
                return bmd.Template;
            }

            return null;
        }

        private Expression MakeTypeTest(StandardRule<T> rule, object[] args)
        {
            return ConstantExpression.Constant(true);
        }
    }
}

